允许模板参数本身是类模板。同样，以堆栈类模板作为示例。

要为堆栈使用不同的内部容器，必须两次指定元素类型。因此，要指定内部容器的类型，必须再次传递容器的类型及其元素的类型:

\begin{lstlisting}[style=styleCXX]
Stack<int, std::vector<int>> vStack; // integer stack that uses a vector
\end{lstlisting}

使用模板模板参数，可以通过指定容器的类型来声明Stack类模板，无需重新指定容器元素的类型:

\begin{lstlisting}[style=styleCXX]
Stack<int, std::vector> vStack; // integer stack that uses a vector
\end{lstlisting}

为此，必须将第二个模板参数指定为一个双重模板参数。如下所示:

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}C++17前，这个版本实现有一个问题(稍后会进行解释)。但是，这只影响默认值std::deque。因此，在讨论如何在C++17前处理它前，可以用这个默认值来说明模板参数的一般特性。
\end{tcolorbox}

\hspace*{\fill} \\ %插入空行
\noindent
\textit{basics/stack8decl.hpp}
\begin{lstlisting}[style=styleCXX]
template<typename T,
		template<typename Elem> class Cont = std::deque>
class Stack {
private:
	Cont<T> elems; // elements
	
public:
	void push(T const&); // push element
	void pop(); // pop element
	T const& top() const; // return top element
	bool empty() const { // return whether the stack is empty
		return elems.empty();
	}
	...
};
\end{lstlisting}

不同的是，第二个模板参数声明为类模板:

\begin{lstlisting}[style=styleCXX]
template<typename Elem> class Cont
\end{lstlisting}

默认值从std::deque<T>变为std::deque。这个参数必须是类模板，其作为第一个模板参数类型的实例化:

\begin{lstlisting}[style=styleCXX]
Cont<T> elems;
\end{lstlisting}

使用第一个模板参数实例化第二个模板参数是本例的特别之处。通常，可以使用类模板中的类型实例化双重模板参数。

通常，可以使用关键字class来代替typename作为模板参数。C++11前，Cont只能用类模板名称替代。

\begin{lstlisting}[style=styleCXX]
template<typename T,
		template<class Elem> class Cont = std::deque>
class Stack { // OK
	...
};
\end{lstlisting}

C++11后，也可以用别名模板的名称来替换Cont。直到C++17才做了相应的更改，允许使用关键字typename，而不是class，来声明一个模板模板参数:

\begin{lstlisting}[style=styleCXX]
template<typename T,
		template<typename Elem> typename Cont = std::deque>
class Stack { // ERROR before C++17
	...
};
\end{lstlisting}

这两种的含义完全相同:使用class或typename并不阻止指定别名模板作为与Cont对应的参数。

因为双重模板参数没有使用，习惯上会省略其名字(除非提供了文档):

\begin{lstlisting}[style=styleCXX]
template<typename T,
		template<typename> class Cont = std::deque>
class Stack {
	...
};
\end{lstlisting}

必须修改相应地成员函数。因此，必须将第二个模板参数指定为双重模板参数，这同样适用于成员函数的实现。例如，push()成员函数的实现:

\begin{lstlisting}[style=styleCXX]
template<typename T, template<typename> class Cont>
void Stack<T,Cont>::push (T const& elem)
{
	elems.push_back(elem); // append copy of passed elem
}
\end{lstlisting}

虽然双重模板参数是类模板或别名模板的占位符，但函数模板或变量模板没有相应的占位符。

\hspace*{\fill} \\ %插入空行
\noindent
\textbf{双重模板参数的匹配}

若使用新版Stack，可能会得到一个错误消息，默认值std::deque与双重模板参数Cont不兼容。问题是在C++17之前，双重模板参数必须与它所替代的模板参数完全匹配，一些例外与可变参数模板参数相关(参见12.3.4节)。没有考虑双重模板参数的默认模板参数，因此不考虑具有默认值的参数无法实现匹配(在C++17中，会考虑默认参数)。

本例中，C++17前的问题是std::deque模板有多个参数:第二个参数(用于描述分配器)有一个默认值，但在C++17前，将std::deque与Cont参数匹配时没有考虑到这一点。

不过，有一个解决办法。可以重写类声明，使Cont参数包含两个模板参数的容器:

\begin{lstlisting}[style=styleCXX]
template<typename T,
	template<typename Elem,
		typename Alloc = std::allocator<Elem>>
	class Cont = std::deque>
class Stack {
private:
	Cont<T> elems; // elements
	...
};
\end{lstlisting}

同样，可以省略Alloc，因为这里没有使用它。

堆栈模板的最终版本(包括不同元素类型堆栈赋值的成员模板)是这样的:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{basics/stack9.hpp}
\begin{lstlisting}[style=styleCXX]
#include <deque>
#include <cassert>
#include <memory>

template<typename T,
	template<typename Elem,
		typename = std::allocator<Elem>>
	class Cont = std::deque>
class Stack {
private:
	Cont<T> elems; // elements
	
public:
	void push(T const&); // push element
	void pop(); // pop element
	T const& top() const; // return top element
	bool empty() const { // return whether the stack is empty
		return elems.empty();
	}

	// assign stack of elements of type T2
	template<typename T2,
		template<typename Elem2,
			typename = std::allocator<Elem2>
		>class Cont2>
	Stack<T,Cont>& operator= (Stack<T2,Cont2> const&);
	// to get access to private members of any Stack with elements of type T2:
	template<typename, template<typename, typename>class>
	friend class Stack;
};

template<typename T, template<typename,typename> class Cont>
void Stack<T,Cont>::push (T const& elem)
{
	elems.push_back(elem); // append copy of passed elem
}

template<typename T, template<typename,typename> class Cont>
void Stack<T,Cont>::pop ()
{
	assert(!elems.empty());
	elems.pop_back(); // remove last element
}

template<typename T, template<typename,typename> class Cont>
T const& Stack<T,Cont>::top () const
{
	assert(!elems.empty());
	return elems.back(); // return copy of last element
}

template<typename T, template<typename,typename> class Cont>
	template<typename T2, template<typename,typename> class Cont2>
Stack<T,Cont>&
Stack<T,Cont>::operator= (Stack<T2,Cont2> const& op2)
{
	elems.clear(); // remove existing elements
	elems.insert(elems.begin(), // insert at the beginning
				op2.elems.begin(), // all elements from op2
				op2.elems.end());
	return *this;
}
\end{lstlisting}

为了访问op2的所有成员，将所有其他堆栈实例都声明为友元(省略模板参数的名称):

\begin{lstlisting}[style=styleCXX]
template<typename, template<typename, typename>class>
friend class Stack;
\end{lstlisting}

不过，并不是所有的标准容器模板都可以用于Cont参数。例如，std::array就不行，因为它是包含了数组长度的非类型模板参数，在我们的模板参数声明中并不匹配。

下面的代码使用了最终版本的所有功能:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{basics/stack9test.cpp}
\begin{lstlisting}[style=styleCXX]
#include "stack9.hpp"
#include <iostream>
#include <vector>

int main()
{
	Stack<int> iStack; // stack of ints
	Stack<float> fStack; // stack of floats
	
	// manipulate int stack
	iStack.push(1);
	iStack.push(2);
	std::cout << "iStack.top(): " << iStack.top() << '\n';
	
	// manipulate float stack:
	fStack.push(3.3);
	std::cout << "fStack.top(): " << fStack.top() << '\n';
	
	// assign stack of different type and manipulate again
	fStack = iStack;
	fStack.push(4.4);
	std::cout << "fStack.top(): " << fStack.top() << '\n';
	
	// stack for doubless using a vector as an internal container
	Stack<double, std::vector> vStack;
	vStack.push(5.5);
	vStack.push(6.6);
	std::cout << "vStack.top(): " << vStack.top() << '\n';
	
	vStack = fStack;
	std::cout << "vStack: ";
	while (! vStack.empty()) {
		std::cout << vStack.top() << ' ';
		vStack.pop();
	}
	std::cout << '\n';
}
\end{lstlisting}

相应的输出为：

\begin{tcblisting}{commandshell={}}
iStack.top(): 2
fStack.top(): 3.3
fStack.top(): 4.4
vStack.top(): 6.6
vStack: 4.4 2 1
\end{tcblisting}

关于模板参数的进一步讨论和示例，请参见第12.2.3节、第12.3.4节和第19.2.2节。





















